﻿/**
 * Licensed to Open-Ones Group under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Open-Ones Group licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a
 * copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

using System;
using System.Collections.Generic;
using System.Text;

namespace IPresenter
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Reflection;
    using System.Runtime;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Windows.Forms;
    using IPresenter;

    /// <summary>
    /// This class provides hook functions
    /// </summary>
    public class HookProvider
    {
        /// <summary>
        /// Message of window(key down)
        /// </summary>
        private const int WM_KEYDOWN = 0x0100;

        /// <summary>
        /// Hook keyboard
        /// </summary>
        private const int WH_KEYBOARD_LL = 13;

        /// <summary>
        /// Hook get message
        /// </summary>
        private const int WH_GETMESSAGE = 3;

        /// <summary>
        /// Virtual key code of left shift
        /// </summary>
        private const int VK_LSHIFT = 0xA0;

        /// <summary>
        /// Virtual key code of right shift
        /// </summary>
        private const int VK_RSHIFT = 0xA1;

        /// <summary>
        /// Contains keys
        /// </summary>
        private static List<int> KeyPressed;

        /// <summary>
        /// ID of hook
        /// </summary>
        private static int intHookId;

        /// <summary>
        /// Get Key down
        /// </summary>
        private static GetKeyDownEventHandler VirtualKeyDown;

        /// <summary>
        /// Hook Process Keyboard
        /// </summary>
        private static HookProcEventHandle HookKeyDown;

        /// <summary>
        /// Delegate Get key down
        /// </summary>
        /// <param name="virtualKey">Name of command</param>
        public delegate void GetKeyDownEventHandler(string virtualKey);

        /// <summary>
        /// An application-defined function that processes messages sent to a window. 
        /// The WNDPROC type defines a pointer to this callback function.
        /// WindowProc is a placeholder for the application-defined function name. 
        /// </summary>
        /// <param name="messageCode">
        /// The message. For lists of the system-provided messages, see System-Defined Messages.
        /// </param>
        /// <param name="wordParameter">
        /// Additional message information. The contents of this parameter depend on the value of the nCode parameter. 
        /// </param>
        /// <param name="lngParameter">
        /// Additional message information. The contents of this parameter depend on the value of the nCode parameter. 
        /// </param>
        /// <returns>
        /// The return value is the result of the message processing and depends on the message sent.
        /// </returns>
        public delegate int HookProcEventHandle(int messageCode, int wordParameter, IntPtr lngParameter);

        /// <summary>
        /// add and remove event key
        /// </summary>
        public static event KeyEventHandler KeyDown
        {
            add
            {
                RegisterHook();
                EventKey += value;
            }

            remove
            {
                UnHook();
                EventKey -= value;
            }
        }

        /// <summary>
        /// Event key down
        /// </summary>
        private static event KeyEventHandler EventKey;

        /// <summary>
        /// Gets or sets keys were pressed.
        /// </summary>
        public static List<int> KeyPressedList
        {
            get { return HookProvider.KeyPressed; }
            set { HookProvider.KeyPressed = value; }
        }

        /// <summary>
        /// Gets or sets VirtualKeyDown
        /// </summary>
        public static GetKeyDownEventHandler VirtualKeyDownDelegate
        {
            get { return HookProvider.VirtualKeyDown; }
            set { HookProvider.VirtualKeyDown = value; }
        }

        /// <summary>
        /// Installs an application-defined hook procedure into a hook chain. 
        /// You would install a hook procedure to monitor the system for certain types of events. 
        /// These events are associated either with a specific thread or with all threads in the same desktop as the calling thread. 
        /// </summary>
        /// <param name="hookId">The type of hook procedure to be installed</param>
        /// <param name="executeHook">
        /// A pointer to the hook procedure. If the dwThreadId parameter is zero or specifies the identifier of a thread
        /// created by a different process, the lpfn parameter must point to a hook procedure in a DLL. Otherwise, lpfn can point to a hook procedure in the code associated with the current process.
        /// </param>
        /// <param name="handleInstance">
        /// A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL 
        /// if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process. 
        /// </param>
        /// <param name="threadId">
        /// The identifier of the thread with which the hook procedure is to be associated. If this parameter is zero, 
        /// the hook procedure is associated with all existing threads running in the same desktop as the calling thread. 
        /// </param>
        /// <returns>
        /// If the function succeeds, the return value is the handle to the hook procedure. 
        /// If the function fails, the return value is NULL. To get extended error information, call GetLastError.
        /// </returns>
        [DllImport("user32.dll")]
        public static extern int SetWindowsHookEx(int hookId, HookProcEventHandle executeHook, IntPtr handleInstance, int threadId);

        /// <summary>
        /// Removes a hook procedure installed in a hook chain by the SetWindowsHookEx function. 
        /// </summary>
        /// <param name="hookId">A handle to the hook to be removed. This parameter is a hook handle obtained by a previous call to SetWindowsHookEx.</param>
        /// <returns>
        /// true: success
        /// false: fail
        /// </returns>
        [DllImport("user32.dll")]
        public static extern bool UnhookWindowsHookEx(int hookId);

        /// <summary>
        /// Passes the hook information to the next hook procedure in the current hook chain.
        /// A hook procedure can call this function either before or after processing the hook information
        /// </summary>
        /// <param name="hookId">This parameter is ignored.</param>
        /// <param name="wordParameter">
        /// The wordParameter value passed to the current hook procedure. The meaning of this parameter
        /// depends on the type of hook associated with the current hook chain.
        /// </param>
        /// <param name="lngParameter">
        /// The lngParameter value passed to the current hook procedure. The meaning of this parameter
        /// depends on the type of hook associated with the current hook chain.
        /// </param>
        /// <returns>
        /// This value is returned by the next hook procedure in the chain. The current hook procedure must also return this value. The meaning of the return value depends on the hook type.
        /// For more information, see the descriptions of the individual hook procedures.
        /// </returns>
        [DllImport("user32.dll")]
        public static extern int CallNextHookEx(int hookId, int wordParameter, IntPtr lngParameter);

        /// <summary>
        /// Register Hook on PC
        /// </summary>
        public static void RegisterHook()
        {
            if (intHookId == 0)
            {
                KeyPressed = new List<int>();
                HookKeyDown = KeyBoardHookProc;
                intHookId = SetWindowsHookEx(WH_KEYBOARD_LL, HookKeyDown, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
                if (intHookId == 0)
                {
                    MessageBox.Show(Marshal.GetLastWin32Error().ToString("0000"), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly);
                }
            }
        }

        /// <summary>
        /// Unhook on PC
        /// </summary>
        public static void UnHook()
        {
            if (intHookId != 0)
            {
                UnhookWindowsHookEx(intHookId);
            }
        }

        /// <summary>
        /// Retrieves the status of the specified virtual key. The status specifies whether the key is up,
        /// down, or toggled (on, off—alternating each time the key is pressed). 
        /// </summary>
        /// <param name="virtualKeyCode">
        /// Virtual key of key
        /// </param>
        /// <returns>
        /// The return value specifies the status of the specified virtual key, as follows: 
        /// If the high-order bit is 1, the key is down; otherwise, it is up.
        /// If the low-order bit is 1, the key is toggled. A key, such as the CAPS LOCK key, is toggled if it is turned on. The key is off and untoggled if the low-order bit is 0. A toggle key's indicator light (if any) on the keyboard will be on when the key is toggled, and off when the key is untoggled.
        /// </returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        internal static extern short GetKeyState(int virtualKeyCode);

        /// <summary>
        /// Execute process hook
        /// </summary>
        /// <param name="listMessage"> 
        /// The message.For lists of the system-provided messages, see System-Defined Messages
        /// </param>
        /// <param name="wordParameter">
        /// Additional message information. The contents of this parameter depend on the value of the nCode
        /// parameter. 
        /// </param>
        /// <param name="lngParameter">
        /// Additional message information. The contents of this parameter depend on the value of the nCode parameter.
        /// </param>
        /// <returns>Id of hook</returns>
        private static int KeyBoardHookProc(int listMessage, int wordParameter, IntPtr lngParameter)
        {
            if (listMessage >= 0)
            {
                KeyBoardInfo data = (KeyBoardInfo)Marshal.PtrToStructure(lngParameter, typeof(KeyBoardInfo));
                if (wordParameter == WM_KEYDOWN)
                {
                    // Keys keyCode = (Keys)data.vkCode;
                    // KeyEventArgs e = new KeyEventArgs(keyCode);
                    // s_key.Invoke(null, e);
                    string strCommand = string.Empty;
                    if (data.IntVkCode == 116)
                    {
                        if (((GetKeyState(VK_LSHIFT) & 0x8000) == 0x8000) || ((GetKeyState(VK_RSHIFT) & 0x8000) == 0x8000))
                        {
                            strCommand = PresentCommand.RESUME_END_FULL_SCREEN; // "ResumeFullScreen";
                        }
                        else
                        {
                            strCommand = PresentCommand.FULL_SCREEN; // "FullScreen";
                        }
                    }

                    if (data.IntVkCode == 39)
                    {
                        strCommand = PresentCommand.NEXT_SLIDE; // "NextSlide";
                    }

                    if (data.IntVkCode == 37)
                    {
                        strCommand = PresentCommand.PREVIOUS_SLIDE; // "PreviousSlide";
                    }

                    if (data.IntVkCode == 27)
                    {
                        strCommand = PresentCommand.END_FULL_SCREEN; // "EndFullScreen";
                    }

                    if (data.IntVkCode == 13)
                    {
                        strCommand = PresentCommand.GO_TO_SLIDE; // "GotoSlide";
                    }

                    if ((data.IntVkCode >= 48) && (57 >= data.IntVkCode))
                    {
                        KeyPressed.Add(data.IntVkCode);
                    }

                    if (strCommand.Length > 0)
                    {
                        VirtualKeyDown(strCommand);
                    }

                    data.IntVkCode = 0;
                    data.IntDwExtraInfo = 0;
                    data.IntFlags = 0;
                    data.IntScanCode = 0;
                    data.IntTime = 0;
                }
            }

            return CallNextHookEx(intHookId, wordParameter, lngParameter);
        }

        /// <summary>
        /// Information of key
        /// </summary>
        private struct KeyBoardInfo
        {
            /// <summary>
            /// Virtual Key Code
            /// </summary>
            public int IntVkCode;

            /// <summary>
            /// scan code of key
            /// </summary>
            public int IntScanCode;

            /// <summary>
            /// State of key
            /// </summary>
            public int IntFlags;

            /// <summary>
            /// Times press key
            /// </summary>
            public int IntTime;

            /// <summary>
            /// Information of key
            /// </summary>
            public int IntDwExtraInfo;
        }
    }
}
